﻿/* 
 * Copyright (C) 2014 Mehdi El Gueddari
 * http://mehdi.me
 *
 * This software may be modified and distributed under the terms
 * of the MIT license.  See the LICENSE file for details.
 */
using System;
using System.Collections;
using System.Data;
using System.Linq;
using System.Runtime.CompilerServices;
using EntityFramework.DbContextScope.Interfaces;
#if NET40
using System.Runtime.Remoting.Messaging;
#else
using System.Threading;
using System.Threading.Tasks;
#endif
#if EFCore
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking.Internal;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
#elif EF6
using System.Data.Entity.Infrastructure;
using System.Data.Entity.Core.Objects;
using System.Data.Entity;
#endif


namespace EntityFramework.DbContextScope
{
    public class DbContextScope : IDbContextScope
    {
        private bool _disposed;
        private readonly bool _readOnly;
        private bool _completed;
        private readonly bool _nested;
        private readonly DbContextScope _parentScope;
        private readonly DbContextCollection _dbContexts;

        public IDbContextCollection DbContexts => _dbContexts;

        public DbContextScope() : this(null)
        {
        }

        public DbContextScope(IDbContextFactory dbContextFactory) : this(DbContextScopeOption.JoinExisting, false, null, dbContextFactory)
        { }

        public DbContextScope(bool readOnly) : this(readOnly, null)
        {
        }

        public DbContextScope(bool readOnly, IDbContextFactory dbContextFactory) : this(DbContextScopeOption.JoinExisting, readOnly, null, dbContextFactory)
        { }

        public DbContextScope(DbContextScopeOption joiningOption, bool readOnly, IsolationLevel? isolationLevel) : this(joiningOption, readOnly, isolationLevel, null)
        {
        }

        public DbContextScope(DbContextScopeOption joiningOption, bool readOnly, IsolationLevel? isolationLevel, IDbContextFactory dbContextFactory)
        {
            if (isolationLevel.HasValue && joiningOption == DbContextScopeOption.JoinExisting)
                throw new ArgumentException("Cannot join an ambient DbContextScope when an explicit database transaction is required. When requiring explicit database transactions to be used (i.e. when the 'isolationLevel' parameter is set), you must not also ask to join the ambient context (i.e. the 'joinAmbient' parameter must be set to false).");

            _disposed = false;
            _completed = false;
            _readOnly = readOnly;

            _parentScope = GetAmbientScope();
            if (_parentScope != null && joiningOption == DbContextScopeOption.JoinExisting)
            {
                if (_parentScope._readOnly && !_readOnly)
                {
                    throw new InvalidOperationException("无法在只读DbContextScope中嵌套读/写DbContextScope。/Cannot nest a read/write DbContextScope within a read-only DbContextScope.");
                }

                _nested = true;
                _dbContexts = _parentScope._dbContexts;
            }
            else
            {
                _nested = false;
                _dbContexts = new DbContextCollection(readOnly, isolationLevel, dbContextFactory);
            }

            SetAmbientScope(this);
        }

        public int SaveChanges()
        {
            if (_disposed)
                throw new ObjectDisposedException("DbContextScope");
            if (_completed)
                throw new InvalidOperationException("You cannot call SaveChanges() more than once on a DbContextScope. A DbContextScope is meant to encapsulate a business transaction: create the scope at the start of the business transaction and then call SaveChanges() at the end. Calling SaveChanges() mid-way through a business transaction doesn't make sense and most likely mean that you should refactor your service method into two separate service method that each create their own DbContextScope and each implement a single business transaction.");

            // Only save changes if we're not a nested scope. Otherwise, let the top-level scope 
            // decide when the changes should be saved.
            int c = 0;
            if (!_nested)
            {
                c = CommitInternal();
            }

            _completed = true;

            return c;
        }
#if !NET40
        public Task<int> SaveChangesAsync()
        {
            return SaveChangesAsync(CancellationToken.None);
        }

        public async Task<int> SaveChangesAsync(CancellationToken cancelToken)
        {
            if (cancelToken == null)
                throw new ArgumentNullException(nameof(cancelToken));
            if (_disposed)
                throw new ObjectDisposedException("DbContextScope");
            if (_completed)
                throw new InvalidOperationException("You cannot call SaveChanges() more than once on a DbContextScope. A DbContextScope is meant to encapsulate a business transaction: create the scope at the start of the business transaction and then call SaveChanges() at the end. Calling SaveChanges() mid-way through a business transaction doesn't make sense and most likely mean that you should refactor your service method into two separate service method that each create their own DbContextScope and each implement a single business transaction.");

            // Only save changes if we're not a nested scope. Otherwise, let the top-level scope 
            // decide when the changes should be saved.
            int c = 0;
            if (!_nested)
            {
                c = await CommitInternalAsync(cancelToken).ConfigureAwait(false);
            }

            _completed = true;
            return c;
        }
#endif
        private int CommitInternal()
        {
            return _dbContexts.Commit();
        }

#if !NET40
        private Task<int> CommitInternalAsync(CancellationToken cancelToken)
        {
            return _dbContexts.CommitAsync(cancelToken);
        }
#endif

        private void RollbackInternal()
        {
            _dbContexts.Rollback();
        }

#if !EFCore
        public void RefreshEntitiesInParentScope(IEnumerable entities)
        {
            if (entities == null)
                return;

            if (_parentScope == null)
                return;

            if (_nested) // The parent scope uses the same DbContext instances as we do - no need to refresh anything
                         //父作用域使用与我们相同的DbContext实例-不需要刷新任何内容
                return;

            // OK, so we must loop through all the DbContext instances in the parent scope
            // and see if their first-level cache (i.e. their ObjectStateManager) contains the provided entities. 
            // If they do, we'll need to force a refresh from the database. 

            // I'm sorry for this code but it's the only way to do this with the current version of Entity Framework 
            // as far as I can see.

            // What would be much nicer would be to have a way to merge all the modified / added / deleted
            // entities from one DbContext instance to another. NHibernate has support for this sort of stuff 
            // but EF still lags behind in this respect. But there is hope: https://entityframework.codeplex.com/workitem/864

            // NOTE: DbContext implements the ObjectContext property of the IObjectContextAdapter interface explicitely.
            // So we must cast the DbContext instances to IObjectContextAdapter in order to access their ObjectContext.
            // This cast is completely safe.

            //好的，所以我们必须遍历父作用域中的所有DbContext实例
            //并查看其第一级缓存（即其ObjectStateManager）是否包含提供的实体。
            //如果他们这样做了，我们需要从数据库中强制刷新。
            //我很抱歉这段代码，但这是对当前版本的实体框架执行此操作的唯一方法
            //就我所见。

            //更好的方法是合并所有修改/添加/删除的
            //从一个DbContext实例到另一个DbContext实例的实体。NHibernate支持这种东西
            //但EF在这方面仍然落后。但还有希望：https://entityframework.codeplex.com/workitem/864

            //注意：DbContext明确地实现了IObjectContextAdapter接口的ObjectContext属性。
            //因此，我们必须将DbContext实例强制转换为IObjectContextAdapter，以便访问它们的ObjectContext。
            //这个石膏是完全安全的。
#if EFCore
			foreach (DbContext contextInCurrentScope in _dbContexts.InitializedDbContexts.Values)
			{
				DbContext correspondingParentContext = _parentScope._dbContexts.InitializedDbContexts.Values.SingleOrDefault(parentContext => parentContext.GetType() == contextInCurrentScope.GetType());
#elif EF6
            foreach (IDbContext contextInCurrentScope in _dbContexts.InitializedDbContexts.Values)
            {
                IObjectContextAdapter correspondingParentContext = _parentScope._dbContexts.InitializedDbContexts.Values.SingleOrDefault(parentContext => parentContext.GetType() == contextInCurrentScope.GetType());
#endif

                if (correspondingParentContext == null)
                    continue; // No DbContext of this type has been created in the parent scope yet. So no need to refresh anything for this DbContext type.
                              //父作用域中尚未创建此类型的DbContext。因此不需要刷新此DbContext类型的任何内容。

                // Both our scope and the parent scope have an instance of the same DbContext type. 
                // We can now look in the parent DbContext instance for entities that need to
                // be refreshed.

                //我们的作用域和父作用域都有一个相同DbContext类型的实例。
                //我们现在可以在父DbContext实例中查找需要
                //振作起来。
                foreach (object toRefresh in entities)
                {
#if EFCore
					// First, we need to find what the EntityKey for this entity is. 
					// We need this EntityKey in order to check if this entity has
					// already been loaded in the parent DbContext's first-level cache (the ObjectStateManager).
					InternalEntityEntry stateInCurrentScope = contextInCurrentScope.ChangeTracker.GetInfrastructure().TryGetEntry(toRefresh);
					if (stateInCurrentScope != null)
					{
						IKey primaryKey = stateInCurrentScope.EntityType.FindPrimaryKey();
						//var key = stateInCurrentScope.EntityKey;
						//Dictionary<IProperty, object> currentPrimaryKeyValue = primaryKey.Properties.ToDictionary(property => property, property => property.GetGetter().GetClrValue(stateInCurrentScope.Entity));

						// Now we can see if that entity exists in the parent DbContext instance and refresh it.
						InternalEntityEntry stateInParentScope = correspondingParentContext.ChangeTracker.GetInfrastructure().TryGetEntry(primaryKey);
						if (stateInParentScope != null)
						{
							// Only refresh the entity in the parent DbContext from the database if that entity hasn't already been
							// modified in the parent. Otherwise, let the whatever concurrency rules the application uses
							// apply.
							if (stateInParentScope.EntityState == EntityState.Unchanged)
							{
								//correspondingParentContext.ObjectContext.Refresh(RefreshMode.StoreWins, stateInParentScope.Entity);
								throw new NotImplementedException("There is no way of refreshing entities between DbContexts in EF7-rc1.");
							}
						}
					}
#elif EF6
                    ObjectStateEntry stateInCurrentScope;
                    if (contextInCurrentScope.ObjectContext.ObjectStateManager.TryGetObjectStateEntry(toRefresh, out stateInCurrentScope))
                    {
                        var key = stateInCurrentScope.EntityKey;

                        // Now we can see if that entity exists in the parent DbContext instance and refresh it.
                        //现在我们可以查看父DbContext实例中是否存在该实体并刷新它。
                        ObjectStateEntry stateInParentScope;
                        if (correspondingParentContext.ObjectContext.ObjectStateManager.TryGetObjectStateEntry(key, out stateInParentScope))
                        {
                            // Only refresh the entity in the parent DbContext from the database if that entity hasn't already been
                            // modified in the parent. Otherwise, let the whatever concurency rules the application uses
                            // apply.

                            //仅当父DbContext中的实体尚未刷新时，才从数据库中刷新该实体
                            //在父级中修改。否则，让应用程序使用的任何并发规则
                            //申请。
                            if (stateInParentScope.State == EntityState.Unchanged)
                            {
                                correspondingParentContext.ObjectContext.Refresh(RefreshMode.StoreWins, stateInParentScope.Entity);
                            }
                        }
                    }
#endif

                }
            }
        }

#if !NET40
        public async Task RefreshEntitiesInParentScopeAsync(IEnumerable entities)
        {
            // See comments in the sync version of this method for an explanation of what we're doing here.
            //请参阅此方法的同步版本中的注释，以了解我们在这里所做的解释
            if (entities == null)
                return;

            if (_parentScope == null)
                return;

            if (_nested)
                return;

#if EFCore
			foreach (DbContext contextInCurrentScope in _dbContexts.InitializedDbContexts.Values)
			{
				DbContext correspondingParentContext = _parentScope._dbContexts.InitializedDbContexts.Values.SingleOrDefault(parentContext => parentContext.GetType() == contextInCurrentScope.GetType());
#elif EF6
            foreach (IDbContext contextInCurrentScope in _dbContexts.InitializedDbContexts.Values)
            {
                IObjectContextAdapter correspondingParentContext = _parentScope._dbContexts.InitializedDbContexts.Values.SingleOrDefault(parentContext => parentContext.GetType() == contextInCurrentScope.GetType());
#endif

                if (correspondingParentContext == null)
                    continue;

                foreach (object toRefresh in entities)
                {
#if EFCore
					// First, we need to find what the EntityKey for this entity is. 
					// We need this EntityKey in order to check if this entity has
					// already been loaded in the parent DbContext's first-level cache (the ObjectStateManager).
					InternalEntityEntry stateInCurrentScope = contextInCurrentScope.ChangeTracker.GetInfrastructure().TryGetEntry(toRefresh);
					if (stateInCurrentScope != null)
					{
						IKey primaryKey = stateInCurrentScope.EntityType.FindPrimaryKey();
						//var key = stateInCurrentScope.EntityKey;
						//Dictionary<IProperty, object> currentPrimaryKeyValue = primaryKey.Properties.ToDictionary(property => property, property => property.GetGetter().GetClrValue(stateInCurrentScope.Entity));

						// Now we can see if that entity exists in the parent DbContext instance and refresh it.
						InternalEntityEntry stateInParentScope = correspondingParentContext.ChangeTracker.GetInfrastructure().TryGetEntry(primaryKey);
						if (stateInParentScope != null)
						{
							// Only refresh the entity in the parent DbContext from the database if that entity hasn't already been
							// modified in the parent. Otherwise, let the whatever concurrency rules the application uses
							// apply.
							if (stateInParentScope.EntityState == EntityState.Unchanged)
							{
								//correspondingParentContext.ObjectContext.Refresh(RefreshMode.StoreWins, stateInParentScope.Entity);
							    throw new NotImplementedException("There is no way of refreshing entities between DbContexts in EF7.");
							}
						}
					}
#elif EF6
                    ObjectStateEntry stateInCurrentScope;
                    if (contextInCurrentScope.ObjectContext.ObjectStateManager.TryGetObjectStateEntry(toRefresh, out stateInCurrentScope))
                    {
                        var key = stateInCurrentScope.EntityKey;

                        ObjectStateEntry stateInParentScope;
                        if (correspondingParentContext.ObjectContext.ObjectStateManager.TryGetObjectStateEntry(key, out stateInParentScope))
                        {
                            if (stateInParentScope.State == EntityState.Unchanged)
                            {
                                await correspondingParentContext.ObjectContext.RefreshAsync(RefreshMode.StoreWins, stateInParentScope.Entity).ConfigureAwait(false);
                            }
                        }
                    }
#endif
                }
            }
        }
#endif
#endif
        public void Dispose()
        {
            if (_disposed)
                return;

            // Commit / Rollback and dispose all of our DbContext instances
            //提交/回滚并释放所有DbContext实例
            if (!_nested)
            {
                if (!_completed)
                {
                    // Do our best to clean up as much as we can but don't throw here as it's too late anyway.
                    //尽我们所能清理干净，但不要扔到这里，反正已经太晚了。
                    try
                    {
                        if (_readOnly)
                        {
                            // Disposing a read-only scope before having called its SaveChanges() method
                            // is the normal and expected behavior. Read-only scopes get committed automatically.

                            //在调用其SaveChanges（）方法之前释放只读作用域
                            //是正常和预期的行为。只读作用域将自动提交。
                            CommitInternal();
                        }
                        else
                        {
                            // Disposing a read/write scope before having called its SaveChanges() method
                            // indicates that something went wrong and that all changes should be rolled-back.

                            //在调用其SaveChanges（）方法之前释放读/写作用域
                            //指示出现问题，所有更改都应回滚。
                            RollbackInternal();
                        }
                    }
                    catch (Exception e)
                    {
                        System.Diagnostics.Debug.WriteLine(e);
                    }

                    _completed = true;
                }

                _dbContexts.Dispose();
            }

            // Pop ourself from the ambient scope stack
            //从环境作用域堆栈中弹出自己
            DbContextScope currentAmbientScope = GetAmbientScope();
            if (currentAmbientScope != this) // This is a serious programming error. Worth throwing here.
                                             //这是一个严重的编程错误。值得扔在这里。
                throw new InvalidOperationException("DbContextScope instances must be disposed of in the order in which they were created!");

            RemoveAmbientScope();

            if (_parentScope != null)
            {
                if (_parentScope._disposed)
                {
                    /*
					 * If our parent scope has been disposed before us, it can only mean one thing:
					 * someone started a parallel flow of execution and forgot to suppress the
					 * ambient context before doing so. And we've been created in that parallel flow.
					 * 
					 * Since the CallContext flows through all async points, the ambient scope in the 
					 * main flow of execution ended up becoming the ambient scope in this parallel flow
					 * of execution as well. So when we were created, we captured it as our "parent scope". 
					 * 
					 * The main flow of execution then completed while our flow was still ongoing. When 
					 * the main flow of execution completed, the ambient scope there (which we think is our 
					 * parent scope) got disposed of as it should.
					 * 
					 * So here we are: our parent scope isn't actually our parent scope. It was the ambient
					 * scope in the main flow of execution from which we branched off. We should never have seen 
					 * it. Whoever wrote the code that created this parallel task should have suppressed
					 * the ambient context before creating the task - that way we wouldn't have captured
					 * this bogus parent scope.
					 * 
					 * While this is definitely a programming error, it's not worth throwing here. We can only 
					 * be in one of two scenario:
					 * 
					 * - If the developer who created the parallel task was mindful to force the creation of 
					 * a new scope in the parallel task (with IDbContextScopeFactory.CreateNew() instead of 
					 * JoinOrCreate()) then no harm has been done. We haven't tried to access the same DbContext
					 * instance from multiple threads.
					 * 
					 * - If this was not the case, they probably already got an exception complaining about the same
					 * DbContext or ObjectContext being accessed from multiple threads simultaneously (or a related
					 * error like multiple active result sets on a DataReader, which is caused by attempting to execute
					 * several queries in parallel on the same DbContext instance). So the code has already blow up.
					 * 
					 * So just record a warning here. Hopefully someone will see it and will fix the code.
					 */
                    /*

                    *如果我们的父作用域已在我们之前被处理，它只能意味着一件事：
                    *有人启动了一个并行的执行流，忘记了抑制
                    *执行此操作之前的环境上下文。我们是在平行流中被创造出来的。
                    *
                    *由于CallContext流经所有异步点，因此
                    *执行的主要流程最终成为这个并行流程中的环境范围
                    *执行死刑。因此，当我们被创建时，我们将其捕获为我们的“父范围”。
                    *
                    *执行的主要流程在我们的流程仍在进行时完成。什么时候？
                    *执行的主要流程已经完成，环境范围在那里（我们认为是
                    *父作用域）已按原样处理。
                    *
                    *所以我们来了：我们的父作用域实际上不是我们的父作用域。是周围的环境
                    *我们从中分支的主要执行流中的作用域。我们不应该看到
                    *它。不管是谁编写了创建这个并行任务的代码
                    *创建任务之前的环境上下文-这样我们就不会捕获
                    *这个假父作用域。
                    *
                    *虽然这绝对是一个编程错误，但不值得在这里抛出。我们只能
                    *处于以下两种情况之一：
                    *
                    *-如果创建并行任务的开发人员注意强制创建
                    *并行任务中的新作用域（使用IDbContextScopeFactory.CreateNew（）而不是
                    *JoinOrCreate（））则不会造成伤害。我们没有尝试访问相同的DbContext
                    *来自多个线程的实例。
                    *
                    *-如果不是这样的话，他们可能已经有了一个例外
                    *从多个线程同时访问DbContext或ObjectContext（或相关的
                    *数据读取器上的多个活动结果集错误，这是由于尝试执行
                    *在同一个DbContext实例上并行执行多个查询）。所以密码已经爆炸了。
                    *
                    *所以在这里记录一个警告。希望有人能看到它并修复代码。
                    */
                    string message = @"PROGRAMMING ERROR - When attempting to dispose a DbContextScope, we found that our parent DbContextScope has already been disposed! This means that someone started a parallel flow of execution (e.g. created a TPL task, created a thread or enqueued a work item on the ThreadPool) within the context of a DbContextScope without suppressing the ambient context first. 

In order to fix this:
1) Look at the stack trace below - this is the stack trace of the parallel task in question.
2) Find out where this parallel task was created.
3) Change the code so that the ambient context is suppressed before the parallel task is created. You can do this with IDbContextScopeFactory.SuppressAmbientContext() (wrap the parallel task creation code block in this). 

Stack Trace:
" + Environment.StackTrace;

                    System.Diagnostics.Debug.WriteLine(message);
                }
                else
                {
                    SetAmbientScope(_parentScope);
                }
            }

            _disposed = true;

        }

        #region Ambient Context Logic

        /*
		 * This is where all the magic happens. And there is not much of it.
		 * 
		 * This implementation is inspired by the source code of the
		 * TransactionScope class in .NET 4.5.1 (the TransactionScope class
		 * is prior versions of the .NET Fx didn't have support for async
		 * operations).
		 * 
		 * In order to understand this, you'll need to be familiar with the
		 * concept of async points. You'll also need to be familiar with the
		 * ExecutionContext and CallContext and understand how and why they 
		 * flow through async points. Stephen Toub has written an
		 * excellent blog post about this - it's a highly recommended read:
		 * http://blogs.msdn.com/b/pfxteam/archive/2012/06/15/executioncontext-vs-synchronizationcontext.aspx
		 * 
		 * Overview: 
		 * 
		 * We want our DbContextScope instances to be ambient within 
		 * the context of a logical flow of execution. This flow may be 
		 * synchronous or it may be asynchronous.
		 * 
		 * If we only wanted to support the synchronous flow scenario, 
		 * we could just store our DbContextScope instances in a ThreadStatic 
		 * variable. That's the "traditional" (i.e. pre-async) way of implementing
		 * an ambient context in .NET. You can see an example implementation of 
		 * a TheadStatic-based ambient DbContext here: http://coding.abel.nu/2012/10/make-the-dbcontext-ambient-with-unitofworkscope/ 
		 * 
		 * But that would be hugely limiting as it would prevent us from being
		 * able to use the new async features added to Entity Framework
		 * in EF6 and .NET 4.5.
		 * 
		 * So we need a storage place for our DbContextScope instances 
		 * that can flow through async points so that the ambient context is still 
		 * available after an await (or any other async point). And this is exactly 
		 * what CallContext is for.
		 * 
		 * There are however two issues with storing our DbContextScope instances 
		 * in the CallContext:
		 * 
		 * 1) Items stored in the CallContext should be serializable. That's because
		 * the CallContext flows not just through async points but also through app domain 
		 * boundaries. I.e. if you make a remoting call into another app domain, the
		 * CallContext will flow through this call (which will require all the values it
		 * stores to get serialized) and get restored in the other app domain.
		 * 
		 * In our case, our DbContextScope instances aren't serializable. And in any case,
		 * we most definitely don't want them to be flown accross app domains. So we'll
		 * use the trick used by the TransactionScope class to work around this issue.
		 * Instead of storing our DbContextScope instances themselves in the CallContext,
		 * we'll just generate a unique key for each instance and only store that key in 
		 * the CallContext. We'll then store the actual DbContextScope instances in a static
		 * Dictionary against their key. 
		 * 
		 * That way, if an app domain boundary is crossed, the keys will be flown accross
		 * but not the DbContextScope instances since a static variable is stored at the 
		 * app domain level. The code executing in the other app domain won't see the ambient
		 * DbContextScope created in the first app domain and will therefore be able to create
		 * their own ambient DbContextScope if necessary.
		 * 
		 * 2) The CallContext is flow through *all* async points. This means that if someone
		 * decides to create multiple threads within the scope of a DbContextScope, our ambient scope
		 * will flow through all the threads. Which means that all the threads will see that single 
		 * DbContextScope instance as being their ambient DbContext. So clients need to be 
		 * careful to always suppress the ambient context before kicking off a parallel operation
		 * to avoid our DbContext instances from being accessed from multiple threads.
		 * 
		 */
        /*

*这就是所有魔法发生的地方。也没有多少。

*

*此实现的灵感来自

*.NET 4.5.1中的TransactionScope类（TransactionScope类

*以前版本的.NET Fx不支持异步

*操作）。

*

*为了理解这一点，您需要熟悉

*异步点的概念。你还需要熟悉

*ExecutionContext和CallContext并理解它们是如何以及为什么

*流经异步点。Stephen Toub写了一篇

*关于此的优秀博客文章-强烈推荐阅读：

*http://blogs.msdn.com/b/pfxteam/archive/2012/06/15/executioncontext-vs-synchronizationcontext.aspx

*

*概述：

*

*我们希望DbContextScope实例在

*逻辑执行流的上下文。这个流程可能是

*同步或可能是异步的。

*

*如果我们只想支持同步流场景，

*我们可以将DbContextScope实例存储在ThreadStatic中

*变量。这是实现的“传统”（即预异步）方式

*.NET中的环境上下文。您可以看到

*这里是一个基于数据的环境DbContext:http://coding.abel.nu/2012/10/make-the-DbContext-ambient-with-unitofworkscope/

*

*但那将是巨大的限制，因为它会阻止我们

*能够使用添加到实体框架中的新异步功能

*在EF6和.NET 4.5中。

*

*所以我们需要一个存储DbContextScope实例的地方

*它可以流经异步点，因此环境上下文仍然是

*在等待（或任何其他异步点）之后可用。而这正是

*CallContext的用途。

*

*但是，存储DbContextScope实例有两个问题

*在CallContext中：

*

*1）存储在CallContext中的项应该是可序列化的。那是因为

*CallContext不仅通过异步点，而且还通过应用程序域

*边界。一、 e.如果您在另一个应用程序域中进行远程处理调用，则

*CallContext将流经此调用（它将需要它的所有值

*存储以进行序列化）并在其他应用程序域中还原。

*

*在我们的例子中，DbContextScope实例是不可序列化的。无论如何，

*我们最确定的是不想让他们在跨应用程序域飞行。所以我们会

*使用TransactionScope类使用的技巧来解决此问题。

*而不是将DbContextScope实例本身存储在CallContext中，

*我们只为每个实例生成一个唯一的密钥，并将该密钥存储在

*调用上下文。然后我们将实际的DbContextScope实例存储在

*根据他们的钥匙查字典。

*

*这样的话，如果一个应用程序域边界被跨越，那么密钥将被交叉传送

*但不是DbContextScope实例，因为静态变量存储在

*应用程序域级别。在另一个应用程序域中执行的代码将看不到环境

*DbContextScope在第一个应用程序域中创建，因此将能够创建

*如果需要，它们自己的环境DbContextScope。

*

*2）CallContext通过*所有*异步点。这意味着如果有人

*决定在DbContextScope（我们的环境作用域）范围内创建多个线程

*将流经所有线程。这意味着所有线程都将看到

*DbContextScope实例作为它们的环境DbContext。所以客户需要

*在开始并行操作之前，请务必抑制环境上下文

*以避免从多个线程访问DbContext实例。

*

*/


        // Use a ConditionalWeakTable instead of a simple ConcurrentDictionary to store our DbContextScope instances 
        // in order to prevent leaking DbContextScope instances if someone doesn't dispose them properly.
        //
        // For example, if we used a ConcurrentDictionary and someone let go of a DbContextScope instance without 
        // disposing it, our ConcurrentDictionary would still have a reference to it, preventing
        // the GC from being able to collect it => leak. With a ConditionalWeakTable, we don't hold a reference
        // to the DbContextScope instances we store in there, allowing them to get GCed.
        // The doc for ConditionalWeakTable isn't the best. This SO anser does a good job at explaining what 
        // it does: http://stackoverflow.com/a/18613811

        //使用ConditionalWeakTable而不是简单的ConcurrentDictionary来存储DbContextScope实例
        //为了防止DbContextScope实例泄漏，如果有人没有正确地处理它们。
        //
        //例如，如果我们使用ConcurrentDictionary，而有人在没有
        //处理它，我们的ConcurrentDictionary仍然会引用它，防止
        //GC无法收集到它=>泄漏。如果有条件的话，我们不需要参考资料
        //我们存储在其中的DbContextScope实例，允许它们获得GCed。
        //条件允许的文档不是最好的。这张照片很好地解释了
        //是的：http://stackoverflow.com/a/18613811

        private static readonly ConditionalWeakTable<InstanceIdentifier, DbContextScope> DbContextScopeInstances = new ConditionalWeakTable<InstanceIdentifier, DbContextScope>();
#if (NET40 || NETSTANDARD1_0)
        private static readonly string AmbientDbContextScopeKey = "AmbientDbcontext_" + Guid.NewGuid();
#else
        private static readonly AsyncLocal<InstanceIdentifier> _scopeInstanceIdentifier = new AsyncLocal<InstanceIdentifier>();
#endif


        private readonly InstanceIdentifier _instanceIdentifier = new InstanceIdentifier();

        /// <summary>
        /// Makes the provided 'dbContextScope' available as the the ambient scope via the CallContext.
        ///使提供的“dbContextScope”通过CallContext作为环境作用域可用。
        /// </summary>
        internal static void SetAmbientScope(DbContextScope newAmbientScope)
        {
            if (newAmbientScope == null)
                throw new ArgumentNullException(nameof(newAmbientScope));

#if (NET40 || NETSTANDARD1_0)
			var current = CallContext.LogicalGetData(AmbientDbContextScopeKey) as InstanceIdentifier;
			if (current == newAmbientScope._instanceIdentifier)
				return;
			// Store the new scope's instance identifier in the CallContext, making it the ambient scope
			CallContext.LogicalSetData(AmbientDbContextScopeKey, newAmbientScope._instanceIdentifier);
#else
            if (_scopeInstanceIdentifier.Value == newAmbientScope._instanceIdentifier)
                return;
            // Store the new scope's instance identifier in the CallContext, making it the ambient scope
            //将新作用域的实例标识符存储在CallContext中，使其成为环境作用域
            _scopeInstanceIdentifier.Value = newAmbientScope._instanceIdentifier;
#endif

            // Keep track of this instance (or do nothing if we're already tracking it)
            //跟踪此实例（如果已在跟踪，则不执行任何操作）
            DbContextScopeInstances.GetValue(newAmbientScope._instanceIdentifier, key => newAmbientScope);
        }

        /// <summary>
        /// Clears the ambient scope from the CallContext and stops tracking its instance. 
        /// Call this when a DbContextScope is being disposed.
        ///从CallContext中清除环境作用域并停止跟踪其实例。
        ///在释放DbContextScope时调用此函数。
        /// </summary>
        internal static void RemoveAmbientScope()
        {
#if (NET40 || NETSTANDARD1_0)
			var current = CallContext.LogicalGetData(AmbientDbContextScopeKey) as InstanceIdentifier;
			CallContext.LogicalSetData(AmbientDbContextScopeKey, null);
#else
            InstanceIdentifier current = _scopeInstanceIdentifier.Value;
            _scopeInstanceIdentifier.Value = null;
#endif
            // If there was an ambient scope, we can stop tracking it now
            //如果有环境范围，我们现在可以停止跟踪
            if (current != null)
            {
                DbContextScopeInstances.Remove(current);
            }
        }

        /// <summary>
        /// Clears the ambient scope from the CallContext but keeps tracking its instance. Call this to temporarily 
        /// hide the ambient context (e.g. to prevent it from being captured by parallel task).
        ///从CallContext中清除环境作用域，但继续跟踪其实例。暂时将此调用为
        ///隐藏环境上下文（例如，防止它被并行任务捕获）。
        ///</summary>
        internal static void HideAmbientScope()
        {
#if (NET40 || NETSTANDARD1_0)
            CallContext.LogicalSetData(AmbientDbContextScopeKey, null);
#else
            _scopeInstanceIdentifier.Value = null;
#endif
        }

        /// <summary>
        /// Get the current ambient scope or null if no ambient scope has been setup.
        ///获取当前环境作用域，如果未设置环境作用域，则为空。
        /// </summary>
        internal static DbContextScope GetAmbientScope()
        {
            // Retrieve the identifier of the ambient scope (if any)
            //检索环境作用域的标识符（如果有）
#if (NET40 || NETSTANDARD1_0)
            InstanceIdentifier instanceIdentifier = CallContext.LogicalGetData(AmbientDbContextScopeKey) as InstanceIdentifier;
#else
            InstanceIdentifier instanceIdentifier = _scopeInstanceIdentifier.Value;
#endif

            if (instanceIdentifier == null)
                return null; // Either no ambient context has been set or we've crossed an app domain boundary and have (intentionally) lost the ambient context
                             //没有设置环境上下文，或者我们跨越了应用程序域边界，并且（故意）丢失了环境上下文

            // Retrieve the DbContextScope instance corresponding to this identifier
            //检索与此标识符对应的DbContextScope实例

            DbContextScope ambientScope;
            if (DbContextScopeInstances.TryGetValue(instanceIdentifier, out ambientScope))
                return ambientScope;

            // We have an instance identifier in the CallContext but no corresponding instance
            // in our DbContextScopeInstances table. This should never happen! The only place where
            // we remove the instance from the DbContextScopeInstances table is in RemoveAmbientScope(),
            // which also removes the instance identifier from the CallContext. 
            //
            // There's only one scenario where this could happen: someone let go of a DbContextScope 
            // instance without disposing it. In that case, the CallContext
            // would still contain a reference to the scope and we'd still have that scope's instance
            // in our DbContextScopeInstances table. But since we use a ConditionalWeakTable to store 
            // our DbContextScope instances and are therefore only holding a weak reference to these instances, 
            // the GC would be able to collect it. Once collected by the GC, our ConditionalWeakTable will return
            // null when queried for that instance. In that case, we're OK. This is a programming error 
            // but our use of a ConditionalWeakTable prevented a leak.

            //我们在CallContext中有一个实例标识符，但是没有对应的实例
            //在DbContextScopeInstances表中。这不应该发生！唯一的地方
            //我们将实例从removeMbientScope（）中的DbContextScopeInstances表中删除，
            //它还从CallContext中移除实例标识符。
            //
            //只有一种情况可能发生这种情况：有人放弃了DbContextScope
            //实例而不处理它。在这种情况下，CallContext
            //仍然包含对作用域的引用，并且我们仍然拥有该作用域的实例
            //在DbContextScopeInstances表中。但是因为我们使用了一个条件weaktable来存储
            //我们的DbContextScope实例，因此只保留对这些实例的弱引用，
            //GC将能够收集它。一旦被GC收集，我们的ConditionalWeakTable将返回
            //查询该实例时为空。那样的话，我们就没事了。这是一个编程错误
            //但是我们使用了一个条件表来防止泄漏。
            System.Diagnostics.Debug.WriteLine("Programming error detected. Found a reference to an ambient DbContextScope in the CallContext but didn't have an instance for it in our DbContextScopeInstances table. This most likely means that this DbContextScope instance wasn't disposed of properly. DbContextScope instance must always be disposed. Review the code for any DbContextScope instance used outside of a 'using' block and fix it so that all DbContextScope instances are disposed of.");
            return null;
        }

        #endregion
    }

    /*
	 * The idea of using an object reference as our instance identifier 
	 * instead of simply using a unique string (which we could have generated
	 * with Guid.NewGuid() for example) comes from the TransactionScope
	 * class. As far as I can make out, a string would have worked just fine.
	 * I'm guessing that this is done for optimization purposes. Creating
	 * an empty class is cheaper and uses up less memory than generating
	 * a unique string.
	*/

    /*
*使用对象引用作为实例标识符的想法
*而不是简单地使用一个唯一的字符串（我们可以生成
*例如Guid.NewGuid（）来自TransactionScope
*上课。据我所知，一根绳子就行了。
*我猜这是出于优化的目的。创建
*空类比生成
*唯一的字符串。
*/
#if EFCore
		//TODO this can probably be optimised
	internal class InstanceIdentifier
    {
		private readonly string _id = Guid.NewGuid().ToString();

		protected bool Equals(InstanceIdentifier other)
		{
			return string.Equals(_id, other._id);
		}

		public override bool Equals(object obj)
		{
			if (ReferenceEquals(null, obj)) return false;
			if (ReferenceEquals(this, obj)) return true;
			if (obj.GetType() != this.GetType()) return false;
			return Equals((InstanceIdentifier) obj);
		}

		public override int GetHashCode()
		{
			return _id?.GetHashCode() ?? 0;
		}
	}
#elif EF6
    internal class InstanceIdentifier : MarshalByRefObject
    { }
#endif
}

